package com.zxq.common.middleware.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.Map;
import java.util.concurrent.*;

@Slf4j
public abstract class RenewalWatchdog<T extends Lock> {
    protected static final long SHUT_DOWN_WAITING_MILLI = 5000;

    /**
     * 刷新间隔, nanosecond
     */
    protected long renewalIntervalNano;

    /**
     * 续租步长, nanosecond
     */
    protected long renewalStepNano;

    protected ScheduledExecutorService renewalScheduler = new ScheduledThreadPoolExecutor(1);

    protected ThreadPoolExecutor renewalExecutor = createProcessorExecutor();

    protected final Map<String, LockTimer<T>> LOCK_TIMER_MAP;

    /**
     * @param intervalNano nanosecond
     * @param stepSizeNano nanosecond
     * @param mapInitSize map init size
     */
    public RenewalWatchdog(long intervalNano, long stepSizeNano, int mapInitSize) {
        log.info("New RenewalWatchdog initiated. {}, {}, {}", intervalNano, stepSizeNano, mapInitSize);

        this.renewalIntervalNano = intervalNano;
        this.renewalStepNano = stepSizeNano;

        this.LOCK_TIMER_MAP = new ConcurrentHashMap<>(mapInitSize);

        renewalScheduler.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    if (CollectionUtils.isEmpty(LOCK_TIMER_MAP)) {
                        log.info("LOCK_TIMER_MAP empty, no lock need to be renewed.");
                        return;
                    }

                    int counter = 0;
                    for (LockTimer<T> item : LOCK_TIMER_MAP.values()) {
                        log.debug("trying to renew lock, timer: ", item);

                        if (renewal(item)) {
                            counter++;
                        }
                    }

                    log.info("{} locks' lease renewed.", counter);
                } catch (Exception e) {
                    log.warn("renewal failed.", e);
                }
            }
        }, this.renewalIntervalNano, this.renewalIntervalNano, TimeUnit.NANOSECONDS);
    }

    /**
     * @param lock
     * @return
     */
    public void watch(T lock) {
        log.debug("watch lock {}", lock);

        LOCK_TIMER_MAP.put(lock.getName(), createLockTimer(lock));
    }

    public void unWatch(T lock) {
        if (lock == null) {
            log.warn("lock cannot be null.");

            return;
        }

        log.debug("unwatch lock {}", lock);

        LOCK_TIMER_MAP.remove(lock.getName());
    }

    public void destroy() {
        renewalScheduler.shutdown();
        renewalExecutor.shutdown();

        try {
            if (renewalScheduler.awaitTermination(SHUT_DOWN_WAITING_MILLI, TimeUnit.MILLISECONDS)) {
                renewalScheduler.shutdownNow();
                renewalScheduler = null;
            }

            if (renewalExecutor.awaitTermination(SHUT_DOWN_WAITING_MILLI, TimeUnit.MILLISECONDS)) {
                renewalExecutor.shutdownNow();
                renewalExecutor = null;
            }
        } catch (InterruptedException e) {
            log.warn("scheduler.awaitTermination interrupted.");
        } catch (Exception ex) {
            log.warn("scheduler.awaitTermination error.", ex);
        } finally {
            LOCK_TIMER_MAP.clear();
        }

        log.info("RenewalWatchdog destroyed.");
    }

    /**
     * threadpool can be refined
     * @return
     */
    protected ThreadPoolExecutor createProcessorExecutor() {
        return new ThreadPoolExecutor(5, 10, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    }

    /**
     *
     */
    protected boolean renewal(LockTimer<T> timer) {
        if (timer == null) {
            return false;
        }

        if (timer.isExpired()) {
            log.debug("unwatch expired lock, time: {}", timer);

            //need remove timer.
            unWatch(timer.getLock());

            return false;
        }

        //put into renewal threadPool
        renewalExecutor.execute(timer);

        return true;
    }

    protected abstract LockTimer<T> createLockTimer(T lock);
}
